<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

// namespace spica\core\session\store;

/**
 * This class provides MySQL storage backend for session data.
 *
 * @category   spica
 * @package    core
 * @subpackage session\store
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 17, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: MySQL.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaSessionMySQLStore /* implements SpicaSessionStore */
{
    /**
     * Session lifetime.
     *
     * @var int
     */
    protected $_lifeTime;

    /**
     * MySQL driver.
     *
     * @var resource of type mysqli
     */
    protected $_driver;

    /**
     * Storage config.
     *
     * @var array
     */
    private $_config;

    /**
     * Connection established.
     *
     * @var bool
     */
    private $_established = false;

    /**
     * Constructs an object of <code>SpicaSessionMySQLStore</code>.
     *
     * @param array $config Defaults to an empty array
     */
    public function __construct($config = array())
    {
        $default = array(
            'dbpersistent' => false,
            'dbhost' => ini_get('mysqli.default_host'),
            'db' => '',
            'dbuser' => ini_get("mysqli.default_user"),
            'dbpassword' => ini_get("mysqli.default_pw"),
            'dbcharset' => 1,
            'dbport' => ini_get("mysqli.default_port"),
            'dbsocket' => ini_get("mysqli.default_socket"), // http://bugs.archlinux.org/task/17179
            'dbtable' => 'sessions',
            'dbcol_sid' => 'sid',
            'dbcol_sdata' => 'sdata',
            'dbcol_stime' => 'stime'
        );

        $this->_config = array_merge($default, $config);

        // @see http://blogs.sun.com/mandalika/entry/demonstrating_the_features_of_mysql
        if (true === $this->_config['dbpersistent'])
        {
            $this->_config = 'p:' . $config['dbhost'];
        }

        // Optional
        if (true === isset($params['dbsocket']))
        {
            $this->_dbsocket = (string) $params['dbsocket'];
        }
    }

    /**
     * Opens session
     *
     * @param  string $savePath ignored
     * @param  string $sessName ignored
     * @return bool
     */
    public function open($savePath, $sessName)
    {
        $this->_lifeTime = ini_get('session.gc_maxlifetime');
        $db = mysqli_connect($this->_config['dbhost'], $this->_config['dbuser'], $this->_config['dbpassword'], $this->_config['db'], $this->_config['dbport'], $this->_config['dbsocket']);
        
        if (false !== $db)
        {
            $this->_established = true;
            $this->_driver = $db;
            return true;
        }

        trigger_error(sprintf('Unable to connect to MySQL session database "%s" (%s).', mysqli_connect_error(), mysqli_connect_errno()), E_USER_NOTICE);
        return false;
    }

    /**
     * Fetches session data
     *
     * @param  string $sid
     * @return string
     */
    public function read($sid)
    {
        if (false === $this->_established)
        {
            return '';
        }

        $stmt = mysqli_prepare($this->_driver, sprintf("SELECT `%s` as data FROM `%s` " . "WHERE `%s` = ?", $this->_config['dbcol_sdata'], $this->_config['dbtable'], $this->_config['dbcol_sid']));

        if (false !== $stmt)
        {
            if (mysqli_stmt_bind_param($stmt, 's', $sid))
            {
                if (mysqli_stmt_execute($stmt))
                {
                    mysqli_stmt_bind_result($stmt, $data);

                    if (mysqli_stmt_fetch($stmt))
                    {
                        mysqli_stmt_close($stmt);
                        return $data;
                    }
                }
            }

            mysqli_stmt_close($stmt);
        }

        trigger_error("Error occured on retrieving session data from MySQL's '{$this->_config['db']}' database: ".mysqli_error($this->_driver), E_USER_NOTICE);
        return '';
    }

    /**
     * Closes session
     *
     * @return bool
     */
    public function close()
    {
        return true;
    }

    /**
     * Updates session.
     *
     * @param  string $sid Session ID
     * @param  string $data
     * @return bool
     */
    public function write($sid, $data)
    {
        if (false === $this->_established)
        {
            return false;
        }

        $stmt = mysqli_prepare($this->_driver, sprintf("REPLACE INTO `%s` (`%s`, `%s`, `%s`) VALUES (?, ?, %s)", $this->_config['dbtable'], $this->_config['dbcol_sid'], $this->_config['dbcol_sdata'], $this->_config['dbcol_stime'], time()));

        if (false !== $stmt)
        {
            if (mysqli_stmt_bind_param($stmt, 'ss', $sid, $data))
            {
                if (mysqli_stmt_execute($stmt) && mysqli_affected_rows($this->_driver))
                {
                    return true;
                }
            }
        }

        trigger_error("Error occured on writing session data into MySQL's '{$this->_config['db']}' database: ".mysqli_error($this->_driver), E_USER_NOTICE);
        return false;
    }

    /**
     * Destroys session provided with ID.
     *
     * @param  string $sid
     * @return bool
     */
    public function destroy($sid)
    {
        if (false === $this->_established)
        {
            return false;
        }

        $stmt = mysqli_prepare($this->_driver, sprintf("DELETE FROM `%s` WHERE `%s` = ?", $this->_config['dbtable'], $this->_config['dbcol_sid']));

        if (false !== $stmt)
        {
            if (mysqli_stmt_bind_param($stmt, 's', $sid))
            {
                if (mysqli_stmt_execute($stmt))
                {
                    return true;
                }
            }
        }

        trigger_error(sprintf('Unable to destroy session id "%s" (%s).', $sid, mysqli_error($this->_driver)), E_USER_NOTICE);
        return false;
    }

    /**
     * Garbage collection
     *
     * @param  int $sessMaxLifeTime ignored
     * @return bool
     */
    public function gc($sessMaxLifeTime)
    {
        if (false === $this->_established)
        {
            return false;
        }

        $stmt = mysqli_prepare($this->_driver, sprintf("DELETE FROM `%s` WHERE `%s` < ?)", $this->_config['dbtable'], $this->_config['dbcol_stime']));

        if (false !== $stmt)
        {
            $time = time() - $sessMaxLifeTime;
            if (mysqli_stmt_bind_param($stmt, 's', $time))
            {
                if (mysqli_stmt_execute($stmt))
                {
                    return true;
                }
            }
        }

        trigger_error(sprintf('Unable to delete old sessions (%s).', mysqli_error($this->_driver)), E_USER_NOTICE);
        return false;
    }

    /**
     * Creates the session data table
     *
     * @return bool
     */
    public function createTable()
    {
        if (false === $this->_established)
        {
            return false;
        }

        $sql = "
CREATE TABLE IF NOT EXISTS `{$this->_config['dbtable']}` (
  `{$this->_config['dbcol_sid']}` varchar(255) NOT NULL COMMENT 'Session ID',
  `{$this->_config['dbcol_sdata']}` VARCHAR(21000) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'Serialized session array',
  `{$this->_config['dbcol_stime']}` INT(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`{$this->_config['dbcol_sid']}`),
  KEY `time` (`{$this->_config['dbcol_stime']}`)
) ENGINE=MEMORY DEFAULT CHARSET=utf8;
";

        $rs = mysqli_query($this->_driver, $sql);

        if (false !== $rs)
        {
            return false;
        }

        return true;
    }

    /**
     * Gets total session.
     *
     * @return int|false
     */
    public function getSessionCount()
    {
        if (false === $this->_established)
        {
            return false;
        }

        $stmt = mysqli_prepare($this->_driver, sprintf("SELECT COUNT(1) AS sessionCount FROM `%s`", $this->_config['dbtable']));

        if (false !== $stmt)
        {
            if (mysqli_stmt_execute($stmt))
            {
                mysqli_stmt_bind_result($stmt, $sessionCount);

                if (mysqli_stmt_fetch($stmt))
                {
                    mysqli_stmt_close($stmt);
                    return (int) $sessionCount;
                }
            }

            mysqli_stmt_close($stmt);
        }

        trigger_error(sprintf('Unable to get session count (%s).', mysqli_error($this->_driver)), E_USER_NOTICE);
        return false;
    }
}

?>